package org.fitweb.runners;

import org.fitweb.files.FileMaker;
import org.fitweb.files.PathFinderTest;
import org.junit.AfterClass;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.Collection;

import static java.io.File.*;
import static java.lang.System.*;
import static org.apache.commons.io.FileUtils.*;
import static org.fitweb.files.DirectoryScrounger.*;
import static org.fitweb.files.PathFinderTest.*;
import static org.junit.Assert.*;

public class SingleTestRunnerTest {

    private static final String INPUT_DIR = getProperty("user.dir") + "/input";
    private static final String OUTPUT_DIR = getProperty("user.dir") + "/output";
    static final File NOT_A_FILE = new File("Not a file");
    private FileMaker make = new FileMaker();

    @AfterClass
    public static void removeTestDirectories() throws IOException {
        deleteDirectory(new File(INPUT_DIR));
        deleteDirectory(new File(OUTPUT_DIR));
        deleteDirectory(new File("very"));
    }

    @Test
    public void runningTestsCopiesTheCorrectResourcesToTheCorrectPlaces() throws Exception {
        File target = new File("out/test-results");
        deleteDirectory(target);
        target.mkdirs();

        new SingleTestRunner("tests", "out/test-results", "nested/Another Very Short Test.html").runSingleTest();

        File nestedFolder = new File(target, "nested");
        assertEquals(NOT_A_FILE, find("support.js", nestedFolder));
        assertEquals(NOT_A_FILE, find("fitweb.css", nestedFolder));
        assertNotNull(find("empty.css", nestedFolder));
    }

    @SuppressWarnings("unchecked")
    private File find(final String fileName, File target) {
        Collection<File> found = (Collection<File>) listFiles(target, new ResourcesFilter() {
            @Override public boolean accept(File file) {
                return super.accept(file) && fileName.equals(file.getName());
            }
        }, ResourcesFilter.INSTANCE);
        assertTrue("Found " + found.size() + " occurrences of " + fileName, found.size() < 2);
        return found.size() == 0 ? NOT_A_FILE : found.iterator().next();
    }

    @Test
    public void inputFoldersAndFileCannotBeBlank() throws Exception {
        given(INPUT_DIR, OUTPUT_DIR, null).expectError("None of the input paths can be blank");
        given(INPUT_DIR, OUTPUT_DIR, "   ").expectError("None of the input paths can be blank");
        given(INPUT_DIR, OUTPUT_DIR, "").expectError("None of the input paths can be blank");
    }

    @Test
    public void supportsNestedTestFoldersForAbsoluteTestPaths() {
        given(INPUT_DIR, OUTPUT_DIR, absolute(INPUT_DIR, "nested/file.html"))
                .expectTestFile("nested/file.html")
                .expectOutputFolder(OUTPUT_DIR + "/nested");

        given(INPUT_DIR, OUTPUT_DIR, absolute(INPUT_DIR, "file.html"))
                .expectTestFile("file.html")
                .expectOutputFolder(OUTPUT_DIR);

        given(INPUT_DIR, OUTPUT_DIR, absolute(INPUT_DIR, "very/deeply/nested/file.html"))
                .expectTestFile("very/deeply/nested/file.html")
                .expectOutputFolder(OUTPUT_DIR + "/very/deeply/nested");
    }

    @Test
    public void supportsNestedTestFoldersForRelativeTestPaths() {
        given(INPUT_DIR, OUTPUT_DIR, relative("nested/file.html"))
                .expectTestFile("nested/file.html")
                .expectOutputFolder(OUTPUT_DIR + "/nested");

        given(INPUT_DIR, OUTPUT_DIR, relative("file.html"))
                .expectTestFile("file.html")
                .expectOutputFolder(OUTPUT_DIR);

        given(INPUT_DIR, OUTPUT_DIR, relative("very/deeply/nested/file.html"))
                .expectTestFile("very/deeply/nested/file.html")
                .expectOutputFolder(OUTPUT_DIR + "/very/deeply/nested");
    }

    @Test
    public void supportsRelativeInputAndOutputDirectories() throws Exception {
        given("tests", "out/test-results", "Fixture Gallery.html")
                .expectTestFile("Fixture Gallery.html")
                .expectOutputFolder("out/test-results");
    }

    @Test
    public void inputFileMustBeHtml() throws Exception {
        String invalidFile = absolute(INPUT_DIR, "temp.sausage");
        given(INPUT_DIR, OUTPUT_DIR, invalidFile).expectError("The test file is not html: ");
    }

    @Test
    public void fileNameIsDisplayedCorrectlyInTestOutput() {
        assertEquals("test.html", runnerFor("my/fit/test.html").testName());
        assertEquals("anotherTest.html", runnerFor("anotherTest.html").testName());
        assertEquals("uriTest.html", runnerFor("file:///path/to/my/uriTest.html").testName());
        assertEquals("windowsTest.html", runnerFor("c:\\shitty\\path\\to\\windowsTest.html").testName());
    }

    @Test
    public void synchronisesSourceFilesToTargetExactlyOnce() throws Exception {
        SingleTestRunner.directoriesSynchronised = false;
        File styles = new File("out/test-results/fitweb.css");
        if (styles.exists()) delete(styles);

        new SingleTestRunner("tests", "out/test-results", "Very Short Test.html").runSingleTest();
        assertTrue(styles.exists());

        delete(styles);
        new SingleTestRunner("tests", "out/test-results", "Very Short Test.html").runSingleTest();
        assertFalse(styles.exists());
    }

    private void delete(File file) {
        if (!file.delete()) {
            throw new RuntimeException("Could not delete file: " + file);
        }
    }

    AssertionBuilder given(String basePath, String outputFolder, String relativeFilePath) {
        return new SingleTestRunnerTest.AssertionBuilder(basePath, outputFolder, relativeFilePath);
    }

    public class AssertionBuilder {
        private SingleTestRunner runner;
        private String basePath;
        private String outputFolder;

        private String relativeFilePath;

        public AssertionBuilder(String basePath, String outputFolder, String relativeFilePath) {
            this.basePath = basePath;
            this.outputFolder = outputFolder;
            this.relativeFilePath = relativeFilePath;
        }

        public void expectError(String errorMessage) throws IOException {
            try {
                runner().runSingleTest();
                fail();
            } catch (IllegalArgumentException e) {
                assertTrue("Unexpected error message. Should start with:\n" + errorMessage + "\nbut was:\n" + e.getMessage(), e.getMessage().startsWith(errorMessage));
            }
        }

        private SingleTestRunner runner() {
            if (runner == null) {
                runner = new SingleTestRunner(basePath, outputFolder, relativeFilePath);
            }

            return runner;
        }

        public AssertionBuilder expectTestFile(String testFile) {
            assertEquals("Incorrect test file", safePath(testFile), runner().relativeTestPath);
            return this;
        }

        public AssertionBuilder expectOutputFolder(String outputFolder) {
            assertEquals("Incorrect output folder", safePath(outputFolder), runner().fileOutputPath);
            return this;
        }
    }

    private String absolute(String parentDir, String filename) {
        return make.file(new File(parentDir), filename).getPath();
    }

    private String relative(String filename) {
        return make.file(filename).getPath();
    }

    private SingleTestRunner runnerFor(String filePath) {
        return new SingleTestRunner(INPUT_DIR, OUTPUT_DIR, filePath);
    }
}
